package com.zzkrst.modules.generator.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import com.zzkrst.modules.generator.model.ColumnEntity;
import com.zzkrst.modules.generator.model.GeneratorParam;
import com.zzkrst.modules.generator.model.TableEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成器工具类
 *
 * @author zlt
 */
@Slf4j
public class GenUtils {
    private GenUtils() {
        throw new IllegalStateException("Utility class");
    }

    private final static String FILE_NAME_MODEL = "Model.java.vm";
    private final static String FILE_NAME_MAPPER = "Mapper.java.vm";
    private final static String FILE_NAME_MAPPERXML = "Mapper.xml.vm";
    private final static String FILE_NAME_SERVICE = "Service.java.vm";
    private final static String FILE_NAME_SERVICEIMPL = "ServiceImpl.java.vm";
    private final static String FILE_NAME_CONTROLLER = "Controller.java.vm";
    private final static String FILE_NAME_VUE_LIST = "vue/list.vue.vm";
    private final static String FILE_NAME_VUE_ADD = "vue/add.vue.vm";
    private final static String TEMPLATE_PATH = "template/";
    private final static String PACKAGE = "package";
    private final static String MODULE_NAME = "moduleName";

    public static List<String> getTemplates() {
        List<String> templates = new ArrayList<>();
        templates.add(TEMPLATE_PATH+FILE_NAME_MODEL);
        templates.add(TEMPLATE_PATH+FILE_NAME_MAPPER);
        templates.add(TEMPLATE_PATH+FILE_NAME_MAPPERXML);
        templates.add(TEMPLATE_PATH+FILE_NAME_SERVICE);
        templates.add(TEMPLATE_PATH+FILE_NAME_SERVICEIMPL);
        templates.add(TEMPLATE_PATH+FILE_NAME_CONTROLLER);

        templates.add(TEMPLATE_PATH+FILE_NAME_VUE_LIST);
        templates.add(TEMPLATE_PATH+FILE_NAME_VUE_ADD);

        return templates;
    }

    /**
     * 生成代码
     */
    public static Map<String, String> generatorCode(ZipOutputStream zip,List<Map<String, String>> columns, GeneratorParam param) throws IOException {
        //配置信息
        Configuration config = getConfig();
        boolean hasBigDecimal = false;
        //表信息
        TableEntity tableEntity = new TableEntity();
        tableEntity.setTableName(param.getTableName());
        tableEntity.setComments(param.getTableComment());
        //表名转换成Java类名
        String className = tableToJava(tableEntity.getTableName(), param.getTablePrefix()==null?"":param.getTablePrefix());
        tableEntity.setClassName(className);
        tableEntity.setClassname(StringUtils.uncapitalize(className));

        //列信息
        List<ColumnEntity> columsList = new ArrayList<>();
        for (Map<String, String> column : columns) {
            ColumnEntity columnEntity = new ColumnEntity();
            columnEntity.setColumnName(column.get("columnName"));
            columnEntity.setDataType(column.get("dataType"));
            columnEntity.setComments(column.get("columnComment"));
            columnEntity.setExtra(column.get("extra"));
            if(column.get("isNullable")!=null && column.get("isNullable").toLowerCase().equals("no")){
                columnEntity.setIsRequired("true");
            }else {
                columnEntity.setIsRequired("false");
            }

            //列名转换成Java属性名
            String attrName = columnToJava(columnEntity.getColumnName());
            columnEntity.setAttrName(attrName);
            columnEntity.setAttrname(StringUtils.uncapitalize(attrName));

            //列的数据类型，转换成Java类型
            String attrType = config.getString(columnEntity.getDataType(), "unknowType");
            columnEntity.setAttrType(attrType);
            if (!hasBigDecimal && "BigDecimal".equals(attrType)) {
                hasBigDecimal = true;
            }
            //是否主键
            if ("PRI".equalsIgnoreCase(column.get("columnKey")) && tableEntity.getPk() == null) {
                tableEntity.setPk(columnEntity);
            }

            columsList.add(columnEntity);
        }
        tableEntity.setColumns(columsList);

        //没主键，则第一个字段为主键
        if (tableEntity.getPk() == null) {
            tableEntity.setPk(tableEntity.getColumns().get(0));
        }

        String mainPath = param.getPackageName();
        //封装模板数据
        Map<String, Object> map = new HashMap<>();
        map.put("tableName", tableEntity.getTableName());
        map.put("comments", tableEntity.getComments());
        map.put("pk", tableEntity.getPk());
        map.put("className", tableEntity.getClassName());
        map.put("classname", tableEntity.getClassname());
        map.put("pathName", tableEntity.getClassname());
        map.put("columns", tableEntity.getColumns());
        map.put("hasBigDecimal", hasBigDecimal);
        map.put("toPath", param.getToPath());
        map.put("mainPath", mainPath);
        map.put(PACKAGE, param.getPackageName());
        String[] strings = param.getPackageName().split("\\.");
        map.put(MODULE_NAME, strings[strings.length-1]);
        map.put("author", param.getAuthor());
        map.put("datetime", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));

        // 渲染数据
        return renderData(zip,  map);
    }



    /**
     * 渲染数据
     * @param map 模板参数
     * @return map key-filename value-contents
     * @throws IOException
     */
    private static Map<String, String> renderData(ZipOutputStream zip, Map<String, Object> map) throws IOException {
        // 设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);
        VelocityContext context = new VelocityContext(map);

        // 获取模板列表
        List<String> templates = getTemplates();
        Map<String, String> resultMap = new HashMap<>();

        for (String template : templates) {

            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, CharsetUtil.UTF_8);
            tpl.merge(context, sw);

            // 有些不需要转义
            //替换特殊符号，比如${
            String swStr = sw.toString();
            swStr = swStr.replace("#$#","$");

            // 添加到zip
            String fileName = getFileName(map.get("toPath").toString(),template, map.get("className").toString(), map.get("package").toString(),
                    map.get("moduleName").toString());

            if (isOSLinux()) {
                zip.putNextEntry(new ZipEntry(Objects.requireNonNull(fileName)));
                IoUtil.write(zip, StandardCharsets.UTF_8, false, swStr);
                IoUtil.close(sw);
                zip.closeEntry();
            }else{
                writeLocalFile(fileName,swStr);
            }
            resultMap.put(template, swStr);
        }

        return resultMap;
    }

    public static void writeLocalFile(String path,String str){
        try {
            File file = new File(path);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            if (file.exists() && file.isFile()) {
                file.delete();
            }
            file.createNewFile();
            if(str != null && !"".equals(str)){
                FileWriter fw = new FileWriter(file, true);
                fw.write(str);//写入本地文件中
                fw.flush();
                fw.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    /**
     * 判断当前系统是否为linux
     * @return
     */
    public static boolean isOSLinux() {
        Properties prop = System.getProperties();

        String os = prop.getProperty("os.name");
        if (os != null && os.toLowerCase().indexOf("linux") > -1) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 列名转换成Java属性名
     */
    public static String columnToJava(String columnName) {
        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
    }

    /**
     * 表名转换成Java类名
     */
    public static String tableToJava(String tableName, String tablePrefix) {
        if (StringUtils.isNotBlank(tablePrefix)) {
            tableName = tableName.substring(tablePrefix.length());
        }
        return columnToJava(tableName);
    }

//    /**
//     * 获取配置信息
//     */
//    public static Properties getConfig() {
//
//        String path = "src/main/resources/generator.properties";
//        try {
//            Properties prop = new Properties();
//            // 通过输入缓冲流进行读取配置文件
//            InputStream InputStream = new BufferedInputStream(new FileInputStream(new File(path)));
//            // 加载输入流
//            prop.load(InputStream);
//            return prop;
//        } catch (Exception e) {
//            throw new RuntimeException("获取配置文件失败，", e);
//        }
//    }
    /**
     * 获取配置信息
     */
    public static Configuration getConfig() {
        try {
            return new PropertiesConfiguration("generator.properties");
        } catch (ConfigurationException e) {
            throw new RuntimeException("获取配置文件失败，", e);
        }
    }


    /**
     * 获取文件名
     */
    public static String getFileName(String toPath,String template, String className, String packageName,String moduleName) {
        String packagePath = toPath+File.separator+"src" + File.separator +"main" + File.separator + "java" + File.separator;
        if (StringUtils.isNotBlank(packageName)) {
            packagePath += packageName.replace(".", File.separator)  + File.separator;
        }

        if (template.contains(FILE_NAME_MODEL)) {
            return packagePath + "entity" + File.separator + className + ".java";
        }

        if (template.contains(FILE_NAME_MAPPER)) {
            return packagePath + "mapper" + File.separator + className + "Mapper.java";
        }

        if (template.contains(FILE_NAME_SERVICE)) {
            return packagePath + "service" + File.separator + "I" + className + "Service.java";
        }

        if (template.contains(FILE_NAME_SERVICEIMPL)) {
            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
        }

        if (template.contains(FILE_NAME_CONTROLLER)) {
            return packagePath + "controller" + File.separator + className + "Controller.java";
        }

        if (template.contains(FILE_NAME_MAPPERXML)) {
            return packagePath + "mapper" + File.separator+ "xml" + File.separator + className + "Mapper.xml";
        }

        if (template.contains(FILE_NAME_VUE_LIST)) {
            return toPath+File.separator+"src" + File.separator +"main" + File.separator + "resources" + File.separator +
                    "pages"+ File.separator + className+"List.vue";
        }

        if (template.contains(FILE_NAME_VUE_ADD)) {
            return toPath+File.separator+"src" + File.separator +"main" + File.separator + "resources" + File.separator +
                    "pages"+ File.separator + className+"Add.vue";
        }

        return null;
    }






    public static List<Map<String, String>> queryColumns(String tableName,String driverUrl,String userName,String passWord) {

        List<Map<String, String>> result = new ArrayList<>();
        String driverClassName = "com.mysql.cj.jdbc.Driver";

        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {

            Class.forName(driverClassName);
            con = DriverManager.getConnection(driverUrl, userName, passWord);

            String sql = " select column_name columnName, data_type dataType, column_comment columnComment," +
                    " column_key columnKey, extra,is_nullable as isNullable,column_type as columnType from information_schema.columns\n" +
                    " where table_name = ? and table_schema = (select database()) order by ordinal_position";
            pstmt = con.prepareStatement(sql);
            pstmt.setNString(1, tableName);

            rs = pstmt.executeQuery();
            if(rs == null) {
                return null;
            }
            while (rs.next()) {
                Map<String, String> map = new HashMap<>();
                map.put("columnName",rs.getString("columnName"));
                map.put("dataType",rs.getString("dataType"));
                map.put("columnComment",rs.getString("columnComment"));
                map.put("columnKey",rs.getString("columnKey"));
                map.put("extra",rs.getString("extra"));

                result.add(map);
            }
            return result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            //关闭资源,倒关
            try {
                if(rs != null) rs.close();
                if(pstmt != null) pstmt.close();
                if(con != null) con.close();  //必须要关
            } catch (Exception e) {
            }
        }

    }






}
